/*
 *  linux/kernel/keyboard.S
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 *	Thanks to Alfred Leung for US keyboard patches
 *		Wolfgang Thiel for German keyboard patches
 *		Marc Corsini for the French keyboard
 */
/*
该程序首先根据键盘特殊键（例如 Alt、 Shift、 Ctrl、 Caps 键）的状态设置程序后面要用到的状态标志变量 mode 的值，
然后根据引起键盘中断的按键扫描码，调用已经编排成跳转表的相应扫描码处理子程序，把扫描码对应的字符放入读字符队列(read_q)中。
接下来调用C处理函数do_tty_interrupt，该函数仅包含一个对行规程函数 copy_to_cooked()的调用。

copy_to_cooked()这个行规程函数的主要作用就是把 readq 读缓冲队列中的字符经过适当处理后放入规范模式队列（辅助队列 secondary）中，
并且在处理过程中，若相应终端设备设置了回显标志，还会把字符直接放入写队列（write_q）中，从而在终端屏幕上会显示出刚键入的字符。
*/
#include <linux/config.h>

.text
.globl keyboard_interrupt

/*
 * these are for the keyboard read functions
 */
size	= 1024		/* must be a power of two ! And MUST be the same
			   as in tty_io.c !!!! */
head = 4
tail = 8
proc_list = 12
buf = 16

mode:	.byte 0		/* caps, alt, ctrl and shift mode */
/*
表示大小写转换键(caps)、交换键(alt)、控制键(ctrl)和换档键(shift)的状态。

位7 caps 键按下;
位6 caps 键的状态（应该与 leds 中对应 caps 的标志位一样）；
位5 右alt 键按下；
位4 左alt 键按下；
位3 右ctrl 键按下；
位2 左ctrl 键按下；
位1 右shift 键按下；
位0 左shift 键按下。
*/
leds:	.byte 2		/* num-lock, caps, scroll-lock mode (nom-lock on) */
/*
leds 是用于表示键盘指示灯的状态标志。即表示数字锁定键(num-lock）、大小写转换键(caps-lock）和滚动锁定键(scroll-lock）的 LED 发光管状态。

位7-3全0不用；
位 2 caps-lock；
位 1 num-lock（初始置1，也即设置数字锁定键(num-lock) 发光管为亮）；
位 0 scroll-lock。
*/
e0:	.byte 0
/*
e0是扫描码的前缀字节，表示一个特殊的键被按下或释放，比如方向键或功能键
当扫描码是 Oxe0 或 Oxe1 时，置该标志。其数值表示其后还跟随着1个或2个字符扫描码。

位0=1 收到 0xe0 标志。表示后面还跟随着1个字符
位1=1 收到 Oxe1 标志；表示后面还有2个字符跟随其后
*/

/*
 *  con_int is the real interrupt routine that reads the
 *  keyboard scan-code and converts it into the appropriate
 *  ascii character(s).
 读取键盘扫描码并将其转换为适当的ascii字符，并放置到缓冲区队列中。
 */
keyboard_interrupt:
	pushl %eax
	pushl %ebx
	pushl %ecx
	pushl %edx
	push %ds
	push %es
	movl $0x10,%eax   /* 将 ds、es 段寄存器置为内核数据段。 */
	mov %ax,%ds
	mov %ax,%es
	xor %al,%al		/* %eax is scan code */
	inb $0x60,%al    # 读取按键扫描码（每个键的数字标识符/索引） -> al
	# 判断扫描码值是否是前导码0xe0或0xe1（是否是功能键）
	cmpb $0xe0,%al
	je set_e0   # 前导码0xe0，表示后面还有1个按键
	cmpb $0xe1,%al
	je set_e1   # 前导码0xe1，表示后面还有2个按键
	call *key_table(,%eax,4)  /* 键处理程序 */
	movb $0,e0       # 返回之后，复位 e0 标志
/* 这段程序用于对收到的扫描码做出应答。方法是首先禁止键盘，然后立刻重新允许键盘工作。*/
e0_e1:	inb $0x61,%al  # 从0x61端口读取 PPI 端口B 的状态，其位7 用于允许/禁止(0/1)键盘工作。
	jmp 1f
1:	jmp 1f
1:	orb $0x80,%al
	jmp 1f
1:	jmp 1f
1:	outb %al,$0x61  # al 位7 置位（禁止键盘工作，按键不再接收）
	jmp 1f
1:	jmp 1f
1:	andb $0x7F,%al  # 将al 位7 复位，开启键盘工作
	outb %al,$0x61
	movb $0x20,%al  # 向 8259 中断芯片发送EOI(中断结束）信号，表示当前中断处理完毕，可以继续接收新的中断。
	outb %al,$0x20
	pushl $0                    /* 控制台 tty 号=0，作为参数入栈。 */
	call do_tty_interrupt       /* 将收到数据（读字符队列）转换成规范模式，并存放在规范字符缓冲队列（secondary队列）中。*/
	addl $4,%esp                /* 丢弃入栈的参数，弹出保留的寄存器，并中断返回。 */
	pop %es
	pop %ds
	popl %edx
	popl %ecx
	popl %ebx
	popl %eax
	iret
set_e0:	movb $1,e0         /* 收到扫描前导码 Oxe0 时，设置 e0 标志（位0） */
	jmp e0_e1
set_e1:	movb $2,e0
	jmp e0_e1

/*
 * This routine fills the buffer with max 8 bytes, taken from
 * %ebx:%eax. (%edx is high). The bytes are written in the
 * order %al,%ah,%eal,%eah,%bl,%bh ... until %eax is zero.  
 下面该子程序把 入参 ebx:eax 中的最多8个字符添入缓冲队列（read_q）中，并唤醒该队列的等待进程。
 ebx 是高字，所写入字符的顺序是 al, ah, eal, eah, bl, bh...直到 eax 等于0。
 */
put_queue:
	pushl %ecx
	pushl %edx
	movl table_list,%edx		# read-queue for console ，即 将table_list的地址传送到edx寄存器，即 &(tty_table[0].read_q)
	movl head(%edx),%ecx        # ecx 为 tty_table[0].read_q.head，即console控制台的读队列的head，注意head是一个偏移值（索引值），并不是一个指针
1:	movb %al,buf(%edx,%ecx)     # 将al字符写入到缓冲队列头指针位置处，即控制台读队列的buf[head]处。向队列放置完1个字符后，进行后面的处理和判断：
	incl %ecx					# 头指针前移1字节。
	andl $size-1,%ecx			# 调整头指针。若超出缓冲区末端则绕回开始处。$size-1的值为1023，即10个比特位全部为1，逻辑与表示当ecx进位时，则只取低10位，即绕回开始处
	cmpl tail(%edx),%ecx		# buffer full - discard everything
	je 3f                       # 向缓冲区队列放字符时，如果tail=head，表示队列已满。如果已满，则后面未放入的字符全抛弃。
	shrdl $8,%ebx,%eax          # 将 ebx 中8个比特右移8位到 eax 中，ebx不变。
	je 2f					    # 还有字符吗？若没有（eax等于0）则跳转。
	shrl $8,%ebx                # 将 ebx 值右移＆位，并跳转到标号1继续操作。
	jmp 1b	# 把 ebx:eax 中数据联合右移8个比特（即把 ah 值移到al、bl -> ah、 bh -> bl)，然后重复上面对 al 的处理过程。直到所有字符都处理完后
2:	movl %ecx,head(%edx)        # (ebx:eax中没有字符的处理方式）此时已经将所有的字符放入到读缓冲区队列read_q中，更新head取值
	movl proc_list(%edx),%ecx
	testl %ecx,%ecx             # 判断proc_list（队列的等待进程）是否为NULL
	je 3f
	movl $0,(%ecx)   # 如果不为NULL，则唤醒进程（置该进程为就绪状态，将ecx寄存器指向的内存的4个字节设置为0，task_struct的前4个字节为state成员变量）
3:	popl %edx        # (队列已满的处理方式) 恢复寄存器，调用返回
	popl %ecx
	ret

/*
从这里开始是键跳转表 key_table 中指针对应的各个按键（或松键）处理子程序。

下面这段代码根据 ctrl或 alt 的扫描码，分别设置模式标志 mode 中相应位。
如果在该扫描码之前收到过 Oxe0 扫描码（e0标志置位），则说明按下的是键盘右边的 ctrl或 alt 键，
则对应设置 ctrl 或 alt 在模式标志 mode 中的比特位。
*/
ctrl:	movb $0x04,%al  # 位2 置位，0x4是 mode 中左ctrl 键对应的比特位（位2）。
	jmp 1f
alt:	movb $0x10,%al  # 位4 置位，0x10是 mode 中左alt 键对应的比特位（位4）。
1:	cmpb $0,e0
	je 2f   # 如果e0等于0
	addb %al,%al  # 如果e0不为0（按下的是右边的 ctrl/alt 键），则将al*2，即左移1位。改成置相应右键标志位（位3或位5）
2:	orb %al,mode  # 设置 mode 标志中对应的比特位。
	ret
/*
这段代码处理 ctrl 或 alt 键松开时的扫描码，复位模式标志 mode 中的对应比特位。
在处理时要根据e0标志是否置位来判断是否是键盘右边的 ctrl 或 alt 键。
*/
unctrl:	movb $0x04,%al
	jmp 1f
unalt:	movb $0x10,%al
1:	cmpb $0,e0
	je 2f
	addb %al,%al
2:	notb %al
	andb %al,mode
	ret

lshift:
	orb $0x01,mode
	ret
unlshift:
	andb $0xfe,mode
	ret
rshift:
	orb $0x02,mode
	ret
unrshift:
	andb $0xfd,mode
	ret

# caps键按下
caps:	testb $0x80,mode  # 测试 mode 中位 7是否己置位（即在按下状态）
	jne 1f  # 如果已处于按下状态(置位，ZF不等于0)，则ret返回
	xorb $4,leds     # 翻转 leds 标志中 caps-1ock 比特位（位2）
	xorb $0x40,mode  # 翻转 mode 标志中 caps 键按下的比特位（位6)
	orb $0x80,mode   # mode的位7置位（caps 键已按下）
set_leds:
	call kb_wait  # 等待键盘控制器输入缓冲空
	movb $0xed,%al		/* set leds command */
	outb %al,$0x60
	call kb_wait
	movb leds,%al
	outb %al,$0x60
	ret
uncaps:	andb $0x7f,mode  # 位7 复位
	ret
scroll:
	xorb $1,leds
	jmp set_leds
num:	xorb $2,leds
	jmp set_leds

/*
 *  curosr-key/numeric keypad cursor keys are handled here.
 *  checking for numeric keypad etc.
 这里处理方向键/数字小键盘方向键，检测数字小键盘等。
 参考：表 10-4 XT 键盘扫描码表
 0、判断扫描码是否满足前提：0x47 ≤ al ≤ 0x53
 1、如果按键是小键盘中的del键，则先判断是否是ctrl-alt-del
 2、是否开启小键盘的光标移动按键
	2.1、如果没有开启，则从num_table中查询对应的字符，放置到缓存队列中
	2.2、如果开启，则从cur_table查询对应的字符，处理小键盘的按键按照光标移动处理，放置到缓存队列中
 */
cursor:
	subb $0x47,%al    # al = al - 0x47 （将按键扫描码转换成按键映射表中的索引，例如扫描码0x47的索引为0）
	jb 1f             # 如果 al - 0x47 < 0, 即al < 0x47 ，则不处理
	cmpb $12,%al
	ja 1f             # 如果 al - 0x47 - 12 > 0 ，则不处理
	# 此后，说明  0x47 ≤ al ≤ 0x53
	jne cur2		/* check for ctrl-alt-del */ /* 如果 al 不等于0x53 ，则跳转 */
	testb $0x0c,mode   # 此处，al - 0x47 - 12 = 0 ，即al为0x53（说明Del键按下），再判断是否按下左ctrl键或右ctrl键
	je cur2
	testb $0x30,mode   # 此时，crtl键已经按下，再判断是否按下alt键
	jne reboot         # 此时，说明ctrl+alt+del键都按下，则执行reboot操作
cur2:	cmpb $0x01,e0		/* e0 forces cursor movement （e0置位，表示小键盘启用光标移动）*/
	je cur
	testb $0x02,leds	/* not num-lock forces cursor (num-lock灯亮，表示关闭光标移动)*/
	je cur
	testb $0x03,mode	/* shift forces cursor （shift键按下，启用光标移动）*/
	jne cur
	xorl %ebx,%ebx
	movb num_table(%eax),%al  # 否则查询数字小键盘表（num_table），取键的数字 ASCII 码字符。eax为扫描码减去0x47
	jmp put_queue
1:	ret

# 这段代码处理：光标移动（上下左右、Home、End、PgUp、PgDn）、插入（Ins）或删除键（Del）按键。
cur:	movb cur_table(%eax),%al  /* 取光标字符表cur_table中相应键的代表字符 */
	/*
	若 al ≤ 字符’9’（即al为5、6、2或3），说明是上一页（PgUp）、下一页（PgDn）、 插入（Ins）或删除键（Del），
	则功能字符序列中要添入字符”~”。不过本内核并没有对这些键值进行识别和处理，只是打印~。
	*/
	cmpb $'9,%al
	ja ok_cur     # al > 字符’9’，则正常处理
	movb $'~,%ah  # al：字符；ah：~
ok_cur:	shll $16,%eax   # 左移16位：将 ax 中内容移到 eax 高字中。即：eal：字符；eah：~
	movw $0x5b1b,%ax    # （ah：[；al：ESC）。把'[ esc'放入 ax， 与 eax 高字中字符组成光标移动序列。即："esc [ 字符 ~"或"esc [ 字符" (字符序列的含义，参考《10.4.3.3 终端控制命令》章节的介绍)
	xorl %ebx,%ebx
	jmp put_queue       # 将该字符放入缓冲队列中。

#if defined(KBD_FR)
num_table:
	.ascii "789 456 1230."
#else
num_table:
	.ascii "789 456 1230,"
#endif
cur_table:
	.ascii "HA5 DGC YB623"

/*
 * this routine handles function keys
 把功能键（F1~F12）扫描码变换成转义字符序列并存放到读队列中。
 */
func:
	pushl %eax
	pushl %ecx
	pushl %edx
	call show_stat # 调用显示各任务状态函数 (kernel/sched.c，37行)
	popl %edx
	popl %ecx
	popl %eax
	subb $0x3B,%al  # 键’F1’的扫描码是 0x3B，此处将 al 变成功能键索引号
	jb end_func  # 如果 al < 0x3B
	cmpb $9,%al
	jbe ok_func  # 如果 0x3B ≤ al ≤ 0x3B + 9 ,说明是键’F1’-’F10’的扫描码
	subb $18,%al # al - 0x3B - 18  : F11、 F12 扫描码是 0x57、 0x58
	cmpb $10,%al
	jb end_func  # al - 0x3B - 18 - 10 < 0  ，即al < 0x57
	cmpb $11,%al
	ja end_func  # 如果 al > 0x58
ok_func:  #  键’F1’-’F10’: 0x3B ≤ al ≤ 0x3B + 9  或  键’F11’-’F12’: 0x57 ≤ al ≤ 0x58
	cmpl $4,%ecx		/* check that there is enough room */
	jl end_func  # 如果ecx < 4，需要4个字节的空间
	movl func_table(,%eax,4),%eax
	xorl %ebx,%ebx
	jmp put_queue
end_func:
	ret

/*
 * function keys send F1:'esc [ [ A' F2:'esc [ [ B' etc.
 */
func_table:
	.long 0x415b5b1b,0x425b5b1b,0x435b5b1b,0x445b5b1b
	.long 0x455b5b1b,0x465b5b1b,0x475b5b1b,0x485b5b1b
	.long 0x495b5b1b,0x4a5b5b1b,0x4b5b5b1b,0x4c5b5b1b

/*
扫描码-ASCII字符映射表
*/
#if	defined(KBD_FINNISH)
key_map:
	.byte 0,27
	.ascii "1234567890+'"
	.byte 127,9
	.ascii "qwertyuiop}"
	.byte 0,13,0
	.ascii "asdfghjkl|{"
	.byte 0,0
	.ascii "'zxcvbnm,.-"
	.byte 0,'*,0,32		/* 36-39 */
	.fill 16,1,0		/* 3A-49 */
	.byte '-,0,0,0,'+	/* 4A-4E */
	.byte 0,0,0,0,0,0,0	/* 4F-55 */
	.byte '<
	.fill 10,1,0

shift_map:
	.byte 0,27
	.ascii "!\"#$%&/()=?`"
	.byte 127,9
	.ascii "QWERTYUIOP]^"
	.byte 13,0
	.ascii "ASDFGHJKL\\["
	.byte 0,0
	.ascii "*ZXCVBNM;:_"
	.byte 0,'*,0,32		/* 36-39 */
	.fill 16,1,0		/* 3A-49 */
	.byte '-,0,0,0,'+	/* 4A-4E */
	.byte 0,0,0,0,0,0,0	/* 4F-55 */
	.byte '>
	.fill 10,1,0

alt_map:
	.byte 0,0
	.ascii "\0@\0$\0\0{[]}\\\0"
	.byte 0,0
	.byte 0,0,0,0,0,0,0,0,0,0,0
	.byte '~,13,0
	.byte 0,0,0,0,0,0,0,0,0,0,0
	.byte 0,0
	.byte 0,0,0,0,0,0,0,0,0,0,0
	.byte 0,0,0,0		/* 36-39 */
	.fill 16,1,0		/* 3A-49 */
	.byte 0,0,0,0,0		/* 4A-4E */
	.byte 0,0,0,0,0,0,0	/* 4F-55 */
	.byte '|
	.fill 10,1,0

#elif defined(KBD_US)

key_map:
	.byte 0,27
	.ascii "1234567890-="
	.byte 127,9
	.ascii "qwertyuiop[]"
	.byte 13,0
	.ascii "asdfghjkl;'"
	.byte '`,0
	.ascii "\\zxcvbnm,./"
	.byte 0,'*,0,32		/* 36-39 */
	.fill 16,1,0		/* 3A-49 */
	.byte '-,0,0,0,'+	/* 4A-4E */
	.byte 0,0,0,0,0,0,0	/* 4F-55 */
	.byte '<
	.fill 10,1,0


shift_map:
	.byte 0,27
	.ascii "!@#$%^&*()_+"
	.byte 127,9
	.ascii "QWERTYUIOP{}"
	.byte 13,0
	.ascii "ASDFGHJKL:\""
	.byte '~,0
	.ascii "|ZXCVBNM<>?"
	.byte 0,'*,0,32		/* 36-39 */
	.fill 16,1,0		/* 3A-49 */
	.byte '-,0,0,0,'+	/* 4A-4E */
	.byte 0,0,0,0,0,0,0	/* 4F-55 */
	.byte '>
	.fill 10,1,0

alt_map:
	.byte 0,0
	.ascii "\0@\0$\0\0{[]}\\\0"
	.byte 0,0
	.byte 0,0,0,0,0,0,0,0,0,0,0
	.byte '~,13,0
	.byte 0,0,0,0,0,0,0,0,0,0,0
	.byte 0,0
	.byte 0,0,0,0,0,0,0,0,0,0,0
	.byte 0,0,0,0		/* 36-39 */
	.fill 16,1,0		/* 3A-49 */
	.byte 0,0,0,0,0		/* 4A-4E */
	.byte 0,0,0,0,0,0,0	/* 4F-55 */
	.byte '|
	.fill 10,1,0

#elif defined(KBD_GR)

key_map:
	.byte 0,27
	.ascii "1234567890\\'"
	.byte 127,9
	.ascii "qwertzuiop@+"
	.byte 13,0
	.ascii "asdfghjkl[]^"
	.byte 0,'#
	.ascii "yxcvbnm,.-"
	.byte 0,'*,0,32		/* 36-39 */
	.fill 16,1,0		/* 3A-49 */
	.byte '-,0,0,0,'+	/* 4A-4E */
	.byte 0,0,0,0,0,0,0	/* 4F-55 */
	.byte '<
	.fill 10,1,0


shift_map:
	.byte 0,27
	.ascii "!\"#$%&/()=?`"
	.byte 127,9
	.ascii "QWERTZUIOP\\*"
	.byte 13,0
	.ascii "ASDFGHJKL{}~"
	.byte 0,''
	.ascii "YXCVBNM;:_"
	.byte 0,'*,0,32		/* 36-39 */
	.fill 16,1,0		/* 3A-49 */
	.byte '-,0,0,0,'+	/* 4A-4E */
	.byte 0,0,0,0,0,0,0	/* 4F-55 */
	.byte '>
	.fill 10,1,0

alt_map:
	.byte 0,0
	.ascii "\0@\0$\0\0{[]}\\\0"
	.byte 0,0
	.byte '@,0,0,0,0,0,0,0,0,0,0
	.byte '~,13,0
	.byte 0,0,0,0,0,0,0,0,0,0,0
	.byte 0,0
	.byte 0,0,0,0,0,0,0,0,0,0,0
	.byte 0,0,0,0		/* 36-39 */
	.fill 16,1,0		/* 3A-49 */
	.byte 0,0,0,0,0		/* 4A-4E */
	.byte 0,0,0,0,0,0,0	/* 4F-55 */
	.byte '|
	.fill 10,1,0


#elif defined(KBD_FR)

key_map:
	.byte 0,27
	.ascii "&{\"'(-}_/@)="
	.byte 127,9
	.ascii "azertyuiop^$"
	.byte 13,0
	.ascii "qsdfghjklm|"
	.byte '`,0,42		/* coin sup gauche, don't know, [*|mu] */
	.ascii "wxcvbn,;:!"
	.byte 0,'*,0,32		/* 36-39 */
	.fill 16,1,0		/* 3A-49 */
	.byte '-,0,0,0,'+	/* 4A-4E */
	.byte 0,0,0,0,0,0,0	/* 4F-55 */
	.byte '<
	.fill 10,1,0

shift_map:
	.byte 0,27
	.ascii "1234567890]+"
	.byte 127,9
	.ascii "AZERTYUIOP<>"
	.byte 13,0
	.ascii "QSDFGHJKLM%"
	.byte '~,0,'#
	.ascii "WXCVBN?./\\"
	.byte 0,'*,0,32		/* 36-39 */
	.fill 16,1,0		/* 3A-49 */
	.byte '-,0,0,0,'+	/* 4A-4E */
	.byte 0,0,0,0,0,0,0	/* 4F-55 */
	.byte '>
	.fill 10,1,0

alt_map:
	.byte 0,0
	.ascii "\0~#{[|`\\^@]}"
	.byte 0,0
	.byte '@,0,0,0,0,0,0,0,0,0,0
	.byte '~,13,0
	.byte 0,0,0,0,0,0,0,0,0,0,0
	.byte 0,0
	.byte 0,0,0,0,0,0,0,0,0,0,0
	.byte 0,0,0,0		/* 36-39 */
	.fill 16,1,0		/* 3A-49 */
	.byte 0,0,0,0,0		/* 4A-4E */
	.byte 0,0,0,0,0,0,0	/* 4F-55 */
	.byte '|
	.fill 10,1,0

#else
#error "KBD-type not defined"
#endif
/*
 * do_self handles "normal" keys, ie keys that don't change meaning
 * and which have just one character returns.
 do_self 用于处理“普通”键，也即含义没有变化并且只有一个字符返回的键。
 */
do_self:
# 首先根据 mode 标志选择 alt_map、 shift_map 或 key_map 映射表之一
	lea alt_map,%ebx    /* ebx 为 alt_map的偏移地址 */
	testb $0x20,mode		/* alt-gr */ 
	jne 1f              # 右alt 键同时按下
	lea shift_map,%ebx
	testb $0x03,mode
	jne 1f              # 有shift 键同时按下（左或右）
	lea key_map,%ebx    # 右alt 键未按下、shift键未未按下
# 此处可能是alt_map、shift_map、key_map其一中的字符
1:	movb (%ebx,%eax),%al    # al 为 ASCII字符。
	orb %al,%al
	je none             # al = 0，没有对应的 ASCII 码（NUL字符）
	# 若 ctrl 键已按下或 caps 灯亮，并且字符在’a’--’}’(0x61--0x7D）范围内（小写字母），则将其转成大写字符 (0x41--0x5D) 。
	# 目的是ctrl按下（或 caps 灯亮）时，忽略其他键的大小写
	testb $0x4c,mode		/* ctrl or caps */ 
	je 2f               # ctrl 未按下 且 caps灯未亮
	cmpb $'a,%al
	jb 2f               # al < 字符‘a’
	cmpb $'},%al
	ja 2f               # al > 字符‘}’
	subb $32,%al            # 如果al字符表示的是小写字母，则将 al 转换为大写字符
# 若 ctrl 键已按下，并且字符在'@'~'_' (0x40—-0x5F）之间，即是大写字符，则将其转换为控制字符 (0x00--0×1F)
2:	testb $0x0c,mode		/* ctrl */
	je 3f                # ctrl 未按下时
	cmpb $64,%al
	jb 3f                # al < 64(字符A为64) 
	cmpb $64+32,%al
	jae 3f               # al ≥ 96(字符a为97) 
	subb $64,%al             # 则将其转换为控制字符 (0x00--0×1F)
# 若左 alt 键同时按下，则将字符的位7置位。即此时生成值大于 Ox7f 的扩展字符集中的字符。
3:	testb $0x10,mode		/* left alt */
	je 4f                 # left alt 未按下时
	orb $0x80,%al
# 将 al 中的字符放入读缓冲队列中。
4:	andl $0xff,%eax
	xorl %ebx,%ebx
	call put_queue
none:	ret

/*
 * minus has a routine of it's own, as a 'E0h' before
 * the scan code for minus means that the numeric keypad
 * slash was pushed.
 */
/*
e0 标志未置位，则调用 do_self 对减号符进行普通处理，否则用’/替换减号’-’。
由于放入队列字符数≤4，因此需把 ebx 清零。并将字符放入缓冲队列中。
*/
minus:	cmpb $1,e0
	jne do_self
	movl $'/,%eax
	xorl %ebx,%ebx
	jmp put_queue

/*
 * This table decides which routine to call when a scan-code has been
 * gotten. Most routines just call do_self, or none, depending if
 * they are make or break.
 下面是一张子程序地址跳转表。当取得扫描码后就根据此表调用相应的扫描码处理子程序。
 大多数调用的子程序是 do_self，或者是 none，这起决于是按键 (make）还是释放键 (break）
 */
key_table:
	.long none,do_self,do_self,do_self	/* 00-03 s0 esc 1 2 */
	.long do_self,do_self,do_self,do_self	/* 04-07 3 4 5 6 */
	.long do_self,do_self,do_self,do_self	/* 08-0B 7 8 9 0 */
	.long do_self,do_self,do_self,do_self	/* 0C-0F + ' bs tab */
	.long do_self,do_self,do_self,do_self	/* 10-13 q w e r */
	.long do_self,do_self,do_self,do_self	/* 14-17 t y u i */
	.long do_self,do_self,do_self,do_self	/* 18-1B o p } ^ */
	.long do_self,ctrl,do_self,do_self	/* 1C-1F enter ctrl a s */
	.long do_self,do_self,do_self,do_self	/* 20-23 d f g h */
	.long do_self,do_self,do_self,do_self	/* 24-27 j k l | */
	.long do_self,do_self,lshift,do_self	/* 28-2B { para lshift , */
	.long do_self,do_self,do_self,do_self	/* 2C-2F z x c v */
	.long do_self,do_self,do_self,do_self	/* 30-33 b n m , */
	.long do_self,minus,rshift,do_self	/* 34-37 . - rshift * */
	.long alt,do_self,caps,func		/* 38-3B alt sp caps f1 */
	.long func,func,func,func		/* 3C-3F f2 f3 f4 f5 */
	.long func,func,func,func		/* 40-43 f6 f7 f8 f9 */
	.long func,num,scroll,cursor		/* 44-47 f10 num scr home */
	.long cursor,cursor,do_self,cursor	/* 48-4B up pgup - left */
	.long cursor,cursor,do_self,cursor	/* 4C-4F n5 right + end */
	.long cursor,cursor,cursor,cursor	/* 50-53 dn pgdn ins del */
	.long none,none,do_self,func		/* 54-57 sysreq ? < f11 */
	.long func,none,none,none		/* 58-5B f12 ? ? ? */
	.long none,none,none,none		/* 5C-5F ? ? ? ? */
	.long none,none,none,none		/* 60-63 ? ? ? ? */
	.long none,none,none,none		/* 64-67 ? ? ? ? */
	.long none,none,none,none		/* 68-6B ? ? ? ? */
	.long none,none,none,none		/* 6C-6F ? ? ? ? */
	.long none,none,none,none		/* 70-73 ? ? ? ? */
	.long none,none,none,none		/* 74-77 ? ? ? ? */
	.long none,none,none,none		/* 78-7B ? ? ? ? */
	.long none,none,none,none		/* 7C-7F ? ? ? ? */
	.long none,none,none,none		/* 80-83 ? br br br */
	.long none,none,none,none		/* 84-87 br br br br */
	.long none,none,none,none		/* 88-8B br br br br */
	.long none,none,none,none		/* 8C-8F br br br br */
	.long none,none,none,none		/* 90-93 br br br br */
	.long none,none,none,none		/* 94-97 br br br br */
	.long none,none,none,none		/* 98-9B br br br br */
	.long none,unctrl,none,none		/* 9C-9F br unctrl br br */
	.long none,none,none,none		/* A0-A3 br br br br */
	.long none,none,none,none		/* A4-A7 br br br br */
	.long none,none,unlshift,none		/* A8-AB br br unlshift br */
	.long none,none,none,none		/* AC-AF br br br br */
	.long none,none,none,none		/* B0-B3 br br br br */
	.long none,none,unrshift,none		/* B4-B7 br br unrshift br */
	.long unalt,none,uncaps,none		/* B8-BB unalt br uncaps br */
	.long none,none,none,none		/* BC-BF br br br br */
	.long none,none,none,none		/* C0-C3 br br br br */
	.long none,none,none,none		/* C4-C7 br br br br */
	.long none,none,none,none		/* C8-CB br br br br */
	.long none,none,none,none		/* CC-CF br br br br */
	.long none,none,none,none		/* D0-D3 br br br br */
	.long none,none,none,none		/* D4-D7 br br br br */
	.long none,none,none,none		/* D8-DB br ? ? ? */
	.long none,none,none,none		/* DC-DF ? ? ? ? */
	.long none,none,none,none		/* E0-E3 e0 e1 ? ? */
	.long none,none,none,none		/* E4-E7 ? ? ? ? */
	.long none,none,none,none		/* E8-EB ? ? ? ? */
	.long none,none,none,none		/* EC-EF ? ? ? ? */
	.long none,none,none,none		/* F0-F3 ? ? ? ? */
	.long none,none,none,none		/* F4-F7 ? ? ? ? */
	.long none,none,none,none		/* F8-FB ? ? ? ? */
	.long none,none,none,none		/* FC-FF ? ? ? ? */

/*
 * kb_wait waits for the keyboard controller buffer to empty.
 * there is no timeout - if the buffer doesn't empty, we hang.
 子程序 kb_wait 用于等待键盘控制器缓冲空。不存在超时处理——如果缓冲永远不空的话，程序就会永远等待（死机）

 键盘控制器有 4 个 8 bits 寄存器：
 Status Register 和 Control Register，两者共用一个端口 0x64，读的时候是状态寄存器，写的时候是控制寄存器。
 Input Buffer 和 Output Buffer，两者共用一个端口 0x60，读的时候是输出缓冲器，写的时候是输入缓冲器。
 */
kb_wait:
	pushl %eax
1:	inb $0x64,%al      # 读键盘控制器状态
	testb $0x02,%al    # 测试输入缓冲器是否为空（等于 0）
	jne 1b             # 若不空，则跳转循环等待。
	popl %eax
	ret
/*
 * This routine reboots the machine by asking the keyboard
 * controller to pulse the reset-line low.
 该子程序通过设置键盘控制器，向复位线输出负脉冲，使系统复位重启(reboot），
 */
/*
该子程序往物理内存地址 0x472 处写值 Ox1234。该位置是启动模式（reboot mode）标志字。
在启动过程中 ROM BIOS 会读取该启动模式标志值并根据其值来指导下一步的执行。
如果该值是 Ox1234，则 BIOS 就会跳过内存检测过程而执行热启动(Warm-boot）过程。
如果若该值为0，则执行冷启动(Cold-boot）过程。
*/
reboot:
	call kb_wait
	movw $0x1234,0x472	/* don't do memory check */
	movb $0xfc,%al		/* pulse reset and A20 low */
	outb %al,$0x64
die:	jmp die
